using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using Gtk;
using brainfucker;
using gtkGui;

public partial class MainWindow: Gtk.Window
{	
    private delegate void WriterEvent(char c);
    private class GuiWriter : TextWriter
    {
        public event WriterEvent OnWrite;
        public override void Write (char value)
        {
            if(OnWrite != null) {
                OnWrite(value);
            }
        }
        public override System.Text.Encoding Encoding {
            get {
                return new System.Text.ASCIIEncoding();
            }
        }
    }
    private class GuiReader : TextReader
    {
        private Queue<char> input_queue = new Queue<char>();
        private volatile bool blocking;
        public override int Read ()
        {
            lock(input_queue) {
                if(input_queue.Count == 0) {
                    blocking = true;
                }
            }
            while(blocking) {
            }
            lock(input_queue) {
                return (int)input_queue.Dequeue();
            }
        }
        public void Enqueue(char c)
        {
            lock(input_queue) {
                input_queue.Enqueue(c);
            }
            if(blocking) {
                blocking = false;
            }
        }
    }
    
    private WatermarkTextView input;
    private WatermarkTextView output;
    
    private Interpreter interpreter;
    private GuiWriter gui_writer = new GuiWriter();
    private GuiReader gui_reader = new GuiReader();
    private Thread interpreter_thread;
    private volatile bool interpreting;
    
    public MainWindow (): base (Gtk.WindowType.Toplevel)
	{
        Build ();
        
        input = new WatermarkTextView("Code", input_scrolled_window);
        output = new WatermarkTextView("Output", output_scrolled_window);
        output.Editable = false;
        output.KeyPressEvent += new KeyPressEventHandler(OnOutputKeyPressEvent);
        input_scrolled_window.AddWithViewport(input);
        output_scrolled_window.AddWithViewport(output);
        input_scrolled_window.ShowAll();
        output_scrolled_window.ShowAll();
        
        BuildInterpreter();
	}
    
    private void BuildInterpreter()
    {
        interpreter = new Interpreter();
        gui_writer.OnWrite += delegate (char c) {
            Application.Invoke(delegate {
                output.Buffer.Text += c;
                clear.Sensitive = true;
            });
        };
        interpreter.Input = gui_reader;
        interpreter.Output = gui_writer;
    }
	
	protected void OnDeleteEvent (object sender, DeleteEventArgs a)
	{
		if(interpreter_thread != null && interpreter_thread.IsAlive) {
            interpreter_thread.Abort();
        }
        Application.Quit ();
		a.RetVal = true;
	}

	protected virtual void OnOutputKeyPressEvent(object o, Gtk.KeyPressEventArgs args)
	{
        if(interpreting && args.Event.KeyValue > 31 && args.Event.KeyValue < 256) {
            gui_reader.Enqueue((char)args.Event.KeyValue);
        }
	}
    
    private void StartInterpreting()
    {
        Application.Invoke(delegate {
            inpterpret_label.Text = "Stop _Interpreting";
            inpterpret_label.UseUnderline = true;
            input.Editable = false;
            interpreting = true;
        });
    }
    
    private void StopInterpreting()
    {
        Application.Invoke(delegate {
            inpterpret_label.Text = "_Interpret";
            inpterpret_label.UseUnderline = true;
            input.Editable = true;
            interpreting = false;
        });
    }

	protected virtual void OnInterpretClicked(object sender, System.EventArgs e)
	{
        if(input.Buffer.Text == "") {
            return;
        }
        if(interpreting) {
            if(interpreter_thread != null && interpreter_thread.IsAlive) {
                interpreter_thread.Abort();
                if(output.Buffer.Text.Length > 0 && !output.Buffer.Text.EndsWith("\n")) {
                    output.Buffer.Text += "\n";
                }
            }
            StopInterpreting();
        } else {
            interpreter_thread = new Thread(new ThreadStart(delegate {
                interpreter.Interpret(input.Buffer.Text);
                StopInterpreting();
            }));
            interpreter_thread.Start();
            StartInterpreting();
        }
	}

	protected virtual void OnCompileClicked(object sender, System.EventArgs e)
	{
        CompileDialog dialog = new CompileDialog();
        if(dialog.Run() == (int)ResponseType.Accept) {
            bool save = true;
            if(File.Exists(dialog.Filename)) {
                MessageDialog md = new MessageDialog(this, DialogFlags.Modal,
                    MessageType.Question, ButtonsType.YesNo,
                    "This file already exists. Do you want to overwrite it?");
                if(md.Run() == (int)ResponseType.Yes) {
                    save = true;
                } else {
                    save = false;
                }
                md.Destroy();
            }
            if(save) {
                try {
                    string path = dialog.Filename;
                    if(!path.EndsWith(".exe")) {
                        path += ".exe";
                    }
                    string name = System.IO.Path.GetFileNameWithoutExtension(path);
                    Mono.Cecil.AssemblyDefinition ad = Compiler.Compile(
                        name, input.Buffer.Text,
                        !dialog.Comments, dialog.IntArray, !dialog.EOF, dialog.Embed);
                    Mono.Cecil.AssemblyFactory.SaveAssembly(ad, path);
                } catch {
                }
            }
        }
        dialog.Destroy();
	}

    protected virtual void OnAboutClicked(object sender, System.EventArgs e)
    {
        AboutDialog ad = new AboutDialog();
        ad.Name = "Brainfucker";
        ad.Comments = "What you never knew you didn't needed.";
        ad.Version = Compiler.Version;
        ad.Authors = new string[] { "Scott Peterson" };
        ad.Run();
        ad.Destroy();
    }

    protected virtual void OnSaveClicked(object sender, System.EventArgs e)
    {
        FileChooserDialog dialog = new FileChooserDialog("Save source file",
            this, FileChooserAction.Save,
            "Cancel", ResponseType.Cancel,
            "Save", ResponseType.Accept);
        if(dialog.Run() == (int)ResponseType.Accept) {
            bool save = true;
            if(File.Exists(dialog.Filename)) {
                MessageDialog md = new MessageDialog(this, DialogFlags.Modal,
                    MessageType.Question, ButtonsType.YesNo,
                    "This file already exists. Do you want to overwrite it?");
                if(md.Run() == (int)ResponseType.Yes) {
                    save = true;
                } else {
                    save = false;
                }
                md.Destroy();
            }
            if(save) {
                try {
                    File.WriteAllText(dialog.Filename, input.Buffer.Text);
                } catch {
                }
            }
        }
        dialog.Destroy();
    }

    protected virtual void OnOpenClicked(object sender, System.EventArgs e)
    {
        FileChooserDialog dialog = new FileChooserDialog("Select as source file",
            this, FileChooserAction.Open,
            "Cancel", ResponseType.Cancel,
            "Open", ResponseType.Accept);
        dialog.SelectMultiple = false;
        if(dialog.Run() == (int)ResponseType.Accept && File.Exists(dialog.Filename)) {
            try {
                input.Buffer.Text = File.ReadAllText(dialog.Filename);
            } catch {
            }
        }
        dialog.Destroy();
    }

    protected virtual void OnClearClicked(object sender, System.EventArgs e)
    {
        output.Buffer.Text = "";
        clear.Sensitive = false;
    }

    //FIXME this is a hacky workaround for http://bugzilla.ximian.com/show_bug.cgi?id=81334
    protected virtual void OnScrolledWindowExposeEvent(object o, Gtk.ExposeEventArgs args)
    {
        ScrolledWindow sw = o as ScrolledWindow;
        Viewport vp = sw.Child as Viewport;
        gtkGui.WatermarkTextView wtv = vp.Child as gtkGui.WatermarkTextView;
        wtv.QueueDraw();
    }
}